import fs, { watchFile } from "fs";
import path from "path";
import { appAddSvr, appRemoveSvr } from "../util/util";
import { Application } from "../Application";
import { logger } from "../util/logger";
import { miscCfg, SvrInfo, SvrMsg, SvrMsgId } from "../util/defines";
import { MsgHandler } from "../handler/MsgHandler";
import { TcpClient } from "../tcp/TcpClient";

/**
 * 服务器
 */
export abstract class Server {

    app: Application;
    socket: TcpClient;
    isReged = false;
    msgHandlers: { [handlerName: string]: any };

    private _onStart: () => void;
    private _hbTimer: NodeJS.Timeout;
    private _hbTimeoutTimer: NodeJS.Timeout;
    private _autoWatcher: fs.FSWatcher;
    private _reloadTimers: { [filePath: string]: NodeJS.Timeout } = {};

    constructor(app: Application, onStart?: () => void) {
        this.app = app;
        this._onStart = onStart;
        this.loadMsgHandlers();
    }

    protected connectMaster() {
        let { port, host } = this.app.masterCfg;
        this.socket = new TcpClient(port, host, this.onConnectMaster.bind(this));
        this.socket.on(this.onData.bind(this), this.onClose.bind(this));
    }

    protected onConnectMaster() {
        logger.info(`${this.app.svr.id}成功连接${this.app.masterCfg.id},开始进行注册`, Server, this.onConnectMaster);
        this.socket.sendReg({ token: miscCfg.svrToken, sid: this.app.svr.id });
    }

    /**
     * 发送消息与Master通信
     * @param msgId 
     * @param msg 
     */
    sendToMaster<MsgId extends SvrMsgId>(msgId: MsgId, msg?: SvrMsg<MsgId>) {
        this.socket.send({ msgId, msg });
    }

    private sendHeartbeat() {
        this.sendToMaster(SvrMsgId.heartbeat);
        this._hbTimeoutTimer = setTimeout(this.onHeartbeatTimeout.bind(this), miscCfg.heartbeatTimeout);
    }

    private onHeartbeatTimeout() {
        logger.error("心跳包返回超时", this, this.onHeartbeatTimeout);
        this.socket.close();
    }

    protected onData(data: { msgId: SvrMsgId, msg: SvrMsg }) {
        try {
            let { msgId, msg } = data;
            switch (msgId) {
                case SvrMsgId.regOk:
                    this.isReged = true;
                    let addSvrs = msg as SvrMsg<typeof msgId>;
                    let removeSvrs: SvrInfo[] = [];
                    for (let sid in this.app.svrIdMap) {
                        if (!addSvrs.includes(sid)) {
                            removeSvrs.push(this.app.svrCfgIdMap[sid]);
                        }
                    }
                    for (let i = addSvrs.length; --i >= 0;) {
                        if (this.app.hasSvr(addSvrs[i])) {
                            addSvrs.splice(i, 1);
                        }
                    }
                    for (let sid of addSvrs) {
                        let svr = this.app.svrCfgIdMap[sid];
                        appAddSvr(this.app, svr);
                    }
                    for (let svr of removeSvrs) {
                        appRemoveSvr(this.app, svr);
                    }
                    this.sendHeartbeat();
                    if (this._onStart) {
                        this._onStart();
                        this._onStart = null;
                    }
                    break;

                case SvrMsgId.heartbeat:
                    clearTimeout(this._hbTimeoutTimer);
                    this._hbTimer = setTimeout(this.sendHeartbeat.bind(this), miscCfg.heartbeatTime);
                    break;

                case SvrMsgId.svrAdd:
                    let addSvr = this.app.svrCfgIdMap[msg as SvrMsg<typeof msgId>];
                    appAddSvr(this.app, addSvr);
                    break;

                case SvrMsgId.svrRemove:
                    let removeSvr = this.app.svrCfgIdMap[msg as SvrMsg<typeof msgId>];
                    appRemoveSvr(this.app, removeSvr);
                    this.app.rpcService.disconnect(removeSvr);
                    break;

                case SvrMsgId.cmd:
                    let cmd = msg as SvrMsg<typeof msgId>;
                    if (miscCfg.onCmd) {
                        let res = miscCfg.onCmd(cmd.cmd);
                        res instanceof Promise ? res.then(data => this.sendToMaster(SvrMsgId.cliRes, { cid: cmd.cid, sess: cmd.sess, sid: this.app.svr.id, res: data })) :
                            this.sendToMaster(SvrMsgId.cliRes, { cid: cmd.cid, sess: cmd.sess, sid: this.app.svr.id, res });
                    }
                    break;

                case SvrMsgId.stop:
                    let stop = msg as SvrMsg<typeof msgId>;
                    this.app.isStopping = true;
                    logger.info("停止服务器中", this, this.onData);
                    if (miscCfg.onStop) {
                        let promise = miscCfg.onStop();
                        if (promise instanceof Promise) {
                            promise.then(() => {
                                stop.cid && this.sendToMaster(SvrMsgId.cliRes, { cid: stop.cid, sess: stop.sess, sid: this.app.svr.id });
                                process.exit()
                            });
                        } else {
                            stop.cid && this.sendToMaster(SvrMsgId.cliRes, { cid: stop.cid, sess: stop.sess, sid: this.app.svr.id });
                            process.exit();
                        }
                    } else {
                        stop.cid && this.sendToMaster(SvrMsgId.cliRes, { cid: stop.cid, sess: stop.sess, sid: this.app.svr.id });
                        process.exit();
                    }
                    break;

                case SvrMsgId.reloadMsgCfg:
                    let reloadMsgCfg = msg as SvrMsg<typeof msgId>;
                    this.app.reloadMsgCfg(reloadMsgCfg.reload);
                    this.sendToMaster(SvrMsgId.cliRes, { cid: reloadMsgCfg.cid, sess: reloadMsgCfg.sess, sid: this.app.svr.id });
                    break;

                case SvrMsgId.reloadMsg:
                    let reloadMsg = msg as SvrMsg<typeof msgId>;
                    this.reloadMsgHandlers(reloadMsg.reload);
                    this.sendToMaster(SvrMsgId.cliRes, { cid: reloadMsg.cid, sess: reloadMsg.sess, sid: this.app.svr.id });
                    break;

                case SvrMsgId.reloadRpc:
                    let reloadRpc = msg as SvrMsg<typeof msgId>;
                    let [stype, rpcHandlerName] = reloadRpc.reload.split(".");
                    this.app.rpcService.reloadRpcHandlers(stype, rpcHandlerName);
                    this.sendToMaster(SvrMsgId.cliRes, { cid: reloadRpc.cid, sess: reloadRpc.sess, sid: this.app.svr.id });
                    break;
            }
        } catch (err) {
            logger.error("与Master通信时发生错误" + (err as Error).stack, this, this.onData);
        }
    }

    protected onClose() {
        this.isReged = false;
        clearTimeout(this._hbTimer);
        clearTimeout(this._hbTimeoutTimer);
        if (this.app.isStopping) return;
        logger.info(`与Master断开连接,${miscCfg.serverReconnectTime / 1000}秒后进行重连`, this, this.onClose);
        setTimeout(this.connectMaster.bind(this), miscCfg.serverReconnectTime);
    }

    getMsgPath() {
        return path.resolve(this.app.base, miscCfg.serversPath, this.app.stype, "msg");
    }

    /**
     * 加载消息Handlers
     */
    loadMsgHandlers() {
        this.msgHandlers = {};
        let msgPath = this.getMsgPath();
        if (!fs.existsSync(msgPath)) return;

        //遍历msg目录
        fs.readdirSync(msgPath).forEach(fileName => {
            let filePath = path.resolve(msgPath, fileName);
            this.loadMsgHandler(filePath, fileName);
        })
        miscCfg.autoReloadHandler && this.autoReloadMsgHandlers(msgPath);
    }

    /**
     * 加载单个消息Handler
     * @param filePath 
     * @param fileName 
     * @returns 
     */
    loadMsgHandler(filePath: string, fileName: string) {
        if (!fileName.endsWith(".js") && !fileName.endsWith(".ts")) return false;
        try {
            let cls: any = require(filePath).default;
            delete require.cache[filePath];
            if (!cls || typeof cls !== "function") return false;

            //构建handler
            let handlerName = fileName.slice(0, fileName.length - 3);
            let old = this.msgHandlers[handlerName];
            let handler = new (cls as typeof MsgHandler)(this.app);
            old && handler.onReload(old);
            this.msgHandlers[handlerName] = handler;
            return true;
        } catch (err: any) {
            logger.error(`加载MsgHandler<${fileName}>失败` + err.statck, this, this.loadMsgHandler);
        }
        return false;
    }

    /**
     * 重载消息Handler
     * @param handlerName 
     */
    reloadMsgHandlers(handlerName?: string) {
        if (handlerName && handlerName != "*") {
            //重载单个
            let format = this.app.isTs ? ".ts" : ".js";
            let filePath = path.resolve(this.getMsgPath(), handlerName + format);
            let fileName = handlerName + format;
            if (!fs.existsSync(filePath)) {
                return logger.error(`重载MsgHandler<${handlerName}>失败,文件不存在`, this, this.reloadMsgHandlers);
            }
            this.loadMsgHandler(filePath, fileName);
        } else {
            //重载目录
            logger.info(`重载所有MsgHandler`, this, this.reloadMsgHandlers);
            let msgPath = this.getMsgPath();
            if (!fs.existsSync(msgPath)) return;

            //遍历msg目录
            fs.readdirSync(msgPath).forEach(fileName => {
                let filePath = path.resolve(msgPath, fileName);
                this.loadMsgHandler(filePath, fileName);
            })
        }
    }

    /**
     * 监听目录文件变化自动重载消息Handler
     * @param msgPath 
     */
    private autoReloadMsgHandlers(msgPath: string) {
        if (this._autoWatcher) return;
        this._autoWatcher = fs.watch(msgPath, (event, fileName) => {
            let filePath = path.resolve(msgPath, fileName);
            if (this._reloadTimers[filePath]) {
                clearTimeout(this._reloadTimers[filePath]);
                delete this._reloadTimers[filePath];
            }

            if (event === "change" || (event === "rename" && fs.existsSync(filePath))) {
                this._reloadTimers[filePath] = setTimeout(() => {
                    delete this._reloadTimers[filePath];
                    if (this.loadMsgHandler(filePath, fileName)) {
                        event === "change" ?
                            logger.info(`已重载MsgHandler<${fileName}>`, this, this.autoReloadMsgHandlers) :
                            logger.info(`已加载MsgHandler<${fileName}>`, this, this.autoReloadMsgHandlers);
                    }
                }, 500);
            }
        });
    }

}